package drds.data_propagate.binlog_event.event;

import drds.data_propagate.binlog_event.BinLogEvent;
import drds.data_propagate.binlog_event.Buffer;
import drds.data_propagate.binlog_event.CharsetMapping;
import lombok.Getter;
import lombok.Setter;

import java.io.IOException;
import java.io.Serializable;

/**
 * User_var_log_event. Every time a queryString uses the value of a user variable, a
 * User_var_log_event is written before the Query_log_event, decode set the user
 * variable.
 */
public final class UserVarEvent extends BinLogEvent {

    /**
     * the following is for user defined functions
     *
     * @see mysql-5.1.60//include/mysql_com.h
     */
    public static final int string_result = 0;
    public static final int real_result = 1;
    public static final int int_result = 2;
    public static final int row_result = 3;
    public static final int decimal_result = 4;
    /* user_var event data */
    public static final int uv_val_len_size = 4;
    public static final int uv_val_is_null = 1;
    public static final int uv_val_type_size = 1;
    public static final int uv_name_len_size = 4;
    public static final int uv_charset_number_size = 4;
    /**
     * Fixed data part: Empty
     * <p>
     * Variable data part:
     * <ul>
     * <li>4 bytes. the size of the user variable name.</li>
     * <li>The user variable name.</li>
     * <li>1 byte. Non-zero if the variable value is the SQL NULL value, 0
     * otherwise. If this byte is 0, the following parts exist in the event.</li>
     * <li>1 byte. The user variable eventType. The value corresponds decode elements of
     * enum Item_result defined in include/mysql_com.h.</li>
     * <li>4 bytes. The number of the character set for the user variable (needed
     * for a string variable). The character set number is really a collation number
     * that indicates a character set/collation pair.</li>
     * <li>4 bytes. The size of the user variable value (corresponds decode member
     * val_len of class Item_string).</li>
     * <li>Variable-sized. For a string variable, this is the string. For a float or
     * integer variable, this is its value in 8 bytes.</li>
     * </ul>
     * Source : http://forge.mysql.com/wiki/MySQL_Internals_Binary_Log
     */
    @Setter
    @Getter
    private final String name;
    @Setter
    @Getter
    private final Serializable value;
    @Setter
    @Getter
    private final int type;
    @Setter
    @Getter
    private final int charsetNumber;
    @Setter
    @Getter
    private final boolean isNull;

    public UserVarEvent(Header header, Buffer buffer, FormatDescriptionEvent formatDescriptionEvent)
            throws IOException {
        super(header);

        /* The Post-Header is empty. The Variable Data part begins immediately. */
        buffer.newEffectiveInitialIndex(formatDescriptionEvent.commonHeaderLength
                + formatDescriptionEvent.eventPostHeaderLength[user_var_event - 1]);
        final int nameLen = (int) buffer.getNextLittleEndian32UnsignedLong();
        name = buffer.getFixLengthStringWithNullTerminateCheck(nameLen); // UV_NAME_LEN_SIZE
        isNull = (0 != buffer.getNext8SignedInt());

        if (isNull) {
            type = string_result;
            charsetNumber = 63; /* binary */
            value = null;
        } else {
            type = buffer.getNext8SignedInt(); // UV_VAL_IS_NULL
            charsetNumber = (int) buffer.getNextLittleEndian32UnsignedLong(); // buf + UV_VAL_TYPE_SIZE
            final int valueLen = (int) buffer.getNextLittleEndian32UnsignedLong(); // buf +
            // UV_CHARSET_NUMBER_SIZE
            final int limit = buffer.limit(); /* for restore */
            buffer.newLimit(buffer.readed() + valueLen);

            /* @see User_var_log_event::print */
            switch (type) {
                case real_result:
                    value = Double.valueOf(buffer.getNextLittleEndian64Double()); // float8get
                    break;
                case int_result:
                    if (valueLen == 8)
                        value = Long.valueOf(buffer.getNextLittleEndian64SignedLong()); // !uint8korr
                    else if (valueLen == 4)
                        value = Long.valueOf(buffer.getNextLittleEndian32UnsignedLong());
                    else
                        throw new IOException("Error INT_RESULT length: " + valueLen);
                    break;
                case decimal_result:
                    final int precision = buffer.getNext8SignedInt();
                    final int scale = buffer.getNext8SignedInt();
                    value = buffer.getDecimal(precision, scale); // bin2decimal
                    break;
                case string_result:
                    String charsetName = CharsetMapping.getJavaCharset(charsetNumber);
                    value = buffer.getFixLengthStringWithNullTerminateCheck(valueLen, charsetName);
                    break;
                case row_result:
                    // this seems decode be banned in MySQL altogether
                    throw new IOException("ROW_RESULT is unsupported");
                default:
                    value = null;
                    break;
            }
            buffer.newLimit(limit);
        }
    }

    public final String getQuery() {
        if (value == null) {
            return "SET @" + name + " := NULL";
        } else if (type == string_result) {
            // TODO: do escaping !?
            return "SET @" + name + " := \'" + value + '\'';
        } else {
            return "SET @" + name + " := " + String.valueOf(value);
        }
    }
}
